package cz.proks.easyhelpdesk.gae.core.servlet;

import java.io.IOException;
import java.net.URLDecoder;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.ModelAndView;

import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;

import cz.proks.easyhelpdesk.api.exception.ApiException;
import cz.proks.easyhelpdesk.api.model.OAuth2Token;
import cz.proks.easyhelpdesk.gae.core.manager.OAuth2TokenManager;
import cz.proks.easyhelpdesk.gae.core.util.OAuth2Verifier;

@Controller("/oauth2callback")
public class OAuth2CallbackServlet implements org.springframework.web.servlet.mvc.Controller {
    private final static Logger log = LoggerFactory.getLogger(OAuth2CallbackServlet.class);

    private final static String CALLBACK_PARAM_CODE = "code";
    private final static String CALLBACK_PARAM_STATE = "state";

    private final static String GOOGLE_TOKEN_ENDPOINT = "https://accounts.google.com/o/oauth2/token";

    @Autowired
    private OAuth2TokenManager tokenManager;

    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        log.debug("handleRequest():: Start");

        String requestUrl = getFullUrlFromRequest(request);
        Map<String, String> urlParameters = cz.proks.easyhelpdesk.utils.UrlUtils.getUrlParameters(requestUrl);

        try {
            String oauthTokenEndpoint = getTokenEndpoint(request);
            String authorizationCode = urlParameters.get(CALLBACK_PARAM_CODE);
            String state = URLDecoder.decode(urlParameters.get(CALLBACK_PARAM_STATE), "UTF-8");

            log.debug("handleRequest():: requestUrl: '{}', oauthTokenEndpoint: '{}', authorizationCode: '{}', state: '{}'", new Object[] { requestUrl,
                    oauthTokenEndpoint, authorizationCode, state });

            OAuth2Token token = verifyCode(oauthTokenEndpoint, authorizationCode);

            log.debug("handleRequest():: Retrieved oauth token {}", token);

            tokenManager.deleteAllForProviderId(token.getProviderId());
            createToken(token);
            
            if (StringUtils.hasText(state)) {
                StringBuilder redirectUrl = new StringBuilder();
                redirectUrl.append(state);
                
                if (!state.endsWith("/"))
                    redirectUrl.append("/");
                
                redirectUrl.append(token.getId());
                
                log.debug("handleRequest():: Sending redirect to {}", redirectUrl);

                response.sendRedirect(redirectUrl.toString());
            } else {
                log.warn("No OAuth state received! No redirection will be executed.");
            }
        } catch (Exception e) {
            log.error("Exception occured while processing request! No redirection will be executed", e);
        } finally {
            log.debug("handleRequest():: Exit");
        }

        return null;
    }
    
    private void createToken(OAuth2Token token) throws ApiException {
        Long id = tokenManager.create(token);
        token.setId(id);        
    }

    private String getTokenEndpoint(HttpServletRequest request) {
        return GOOGLE_TOKEN_ENDPOINT;
    }

    private String getFullUrlFromRequest(HttpServletRequest request) {
        StringBuilder builder = new StringBuilder(request.getRequestURL());
        String query = request.getQueryString();

        if (StringUtils.hasText(query))
            builder.append("?").append(query);

        return builder.toString();
    }

    private OAuth2Token verifyCode(String endpointUrl, String authorizationCode) throws ApiException {
        if (GOOGLE_TOKEN_ENDPOINT.equalsIgnoreCase(endpointUrl))
            return verifyGoogleCode(authorizationCode);
        else
            return null;
    }

    private OAuth2Token verifyGoogleCode(String authorizationCode) throws ApiException {
        log.debug("verifyGoogleCode():: Start with authorizationCode '{}'", authorizationCode);

        OAuth2Token token = OAuth2Verifier.verify(GOOGLE_TOKEN_ENDPOINT, authorizationCode);
        
        setTokenProviderId(token);
        
        log.debug("verifyGoogleCode():: Exit with token {}", token);

        return token;
    }
    
    private void setTokenProviderId(OAuth2Token token) {
        if (token == null)
            return;
        
        UserService userService = UserServiceFactory.getUserService();        
        User user = userService.getCurrentUser();
        
        if (user != null)
            token.setProviderId(user.getUserId());        
    }
    
}
